/* Main file for Craig Brett's scripts
* Created by Craig Brett as part of the scripting team for Tactical Battle
* For help with these scripts, please contact either myself or support@blindaudiogames.com
* I will be using XML style commenting for documentation of scripts, hence the triple slashing
* This should allow people using IDEs to receive intellicense style help
*/
// needed since the JS engine doesn't seem to be able
// to look inside objects for its events to execute
// so event names must be in global scope
function cb_after_create_unit(e) {
    return CB.after_create_unit(e);
}
function cb_after_death(e) {
    return CB.after_death(e);
}
function cb_after_perform_skill(e) {
    return CB.after_perform_skill(e);
}
function cb_after_effect_applied(e) {
    return CB.after_effect_applied(e);
}
function cb_after_effect_fizzled(e) {
    return CB.after_effect_fizzled(e);
}
function cb_after_effect_removed(e) {
    return CB.after_effect_removed(e);
}
function cb_after_turn_started(e) {
    return CB.after_turn_started(e);
}
function cb_on_map_key(e) {
    return CB.on_map_key(e);
}
function cb_override_handle_causers(e) {
    return CB.override_handle_causers(e);
}
function cb_override_tb_unit_ai(e) {
    return CB.override_tb_unit_ai(e);
}
var CB;
(function (CB) {
    // global object
    // in case another file or load has made the object before we have, make sure we check
    var cb;
    if (!global.craig_brett) {
        global.craig_brett = new Object();
        cb = global.craig_brett;
        // initialize all the other scripts hashes
        cb.TeamChanges = shared.MakeIntToObjectHash();
        cb.RevertTypes = shared.MakeIntToObjectHash(); // using the unit ID for indexes
        cb.transports = shared.MakeIntToObjectHash(); // transport ID is the index, a transport object is the value
    }
    else
        cb = global.craig_brett;
    function announce_width_and_height(e) {
        /// <summary>Announces the width and height of the map</summary>
        /// <param name="e">The event object</param>
        if (e.k != e.key || e.m != e.mod) {
            return; // it's not the key combination we are supposed to respond to.
        }
        if (shared.Map.Width && shared.Map.Height) {
            var height = shared.Map.Height.toString();
            var width = shared.Map.Width.toString();
            say("This map is " + height + " tiles vertically and " + width + " tiles horizontally. ");
        }
    }
    function move_other_unit(e) {
        /// <summary>Moves a unit away from or towards a skill user or a tile</summary>
        /// <param name="e">The event object</param>
        var where = e.where.toLowerCase();
        if (where == null) {
            say("No where argument specified for move_other_unit.");
            return;
        }
        var source = e.source;
        var target = e.target;
        if (!target || target.Dead)
            return; // don't do moving if there is no target or it's a dead one
        if (target.ScriptFlags.get("move_other_unit_immune"))
            return; // some units can be immune
        var targetEffects = target.Effects;
        if (targetEffects && targetEffects.length > 0) {
            for (var i = 0; i < targetEffects.length; i++) {
                var effect = targetEffects[i];
                if (effect.ScriptFlags.get("move_other_unit_immune"))
                    return; // they've got an effect that makes them immune to being moved
            }
        }
        var targetEquippedItems = target.GetEquippedItems();
        if (targetEquippedItems && targetEquippedItems.length > 0) {
            for (var i = 0; i < targetEquippedItems.length; i++) {
                var item = targetEquippedItems[i];
                if (item.ScriptFlags.get("move_other_unit_immune"))
                    return; // they've got an effect that makes them immune to being moved
            }
        }
        var tilesToMove = e.tiles;
        var skill = e.skill;
        var flags = skill.ScriptFlags;
        if (!(flags.get("move_other_unit_on_fail") || e.success))
            return; // if the moves on fail flag is there or the skill succeeds, then we move the unit
        var direction = 0; // 0 = north, 1 northeast, 2 east, etc.
        if (where == "away") {
            // if they're both on the same tile, pick a direction at random
            if (source.Tile.X == target.Tile.X && source.Tile.Y == target.Tile.Y) {
                direction = Math.round((Math.random() * 8));
            }
            else {
                direction = calculateDirectionInt(target.Tile, source.Tile);
            }
        }
        else if (where == "toward") {
            // if they're already on the same tile, there's nothing we can do
            if (source.Tile.X == target.Tile.X && source.Tile.Y == target.Tile.Y) {
                return;
            }
            direction = calculateDirectionInt(source.Tile, target.Tile);
        }
        else {
            say("Unrecognized where argument " + where);
            return;
        }
        var nextTile, currentTile = target.Tile;
        for (var count = 0; count < tilesToMove; count++) {
            nextTile = getNextTileInDirection(currentTile, direction);
            if (where == "toward" && currentTile.X == source.Tile.X && currentTile.Y == source.Tile.Y) {
                break;
            }
            else if ((currentTile.X != nextTile.X || currentTile.Y != nextTile.Y) && nextTile.IsValidDestinationFor(target)) {
                currentTile = nextTile;
            }
            else {
                break;
            }
        }
        if (currentTile.X != target.Tile.X || currentTile.Y != target.Tile.Y) {
            shared.Map.MoveUnit(target, currentTile.X, currentTile.Y);
        }
    }
    function announce_point(e) {
        /// <summary>Reads out a specific point value of the selected unit</summary>
        /// <param name="e">The event object</param>	
        if (e.k != e.key || e.m != e.mod) {
            return; // it's not the key combination we are supposed to respond to.
        }
        var unit = shared.Map.ReviewTile.Selected;
        if (!unit) {
            say("no unit selected");
            return; // no unit so nothing to do
        }
        var pointName = e.Point;
        if (!pointName) {
            say("No point argument given to announce_point script.");
            return;
        }
        var output = e.Format;
        if (!output) {
            say("No Format argument given to announce_point script.");
            return;
        }
        if (!unit.Points.get(pointName)) {
            say(unit.GetName() + " has no such point " + pointName);
            return;
        }
        var point = unit.Points.get(pointName);
        if (output.indexOf("{0}") != -1) {
            output = output.replace("{0}", point.Current);
        }
        if (output.indexOf("{1}") != -1) {
            output = output.replace("{1}", point.Max);
        }
        say(output);
    }
    function after_create_unit(e) {
        /// <summary>Runs after create logic on script flags (if any)</summary>
        /// <param name="e">The event object</param>
        if (!e.unit) {
            return; // we shouldn't get unitless after creates, but just in case
        }
        var unit = e.unit;
        var flags = unit.ScriptFlags;
        var transportCapacity = flags.get("transport_capacity");
        if (transportCapacity) {
            CB.Transportation.make_transport({ unit: unit, capacity: transportCapacity, onEnter: flags.get("on_enter_transport"), onExit: flags.get("on_exit_transport") });
        }
    }
    CB.after_create_unit = after_create_unit;
    function after_death(e) {
        /// <summary>Runs after death logic on script flags (if any)</summary>
        /// <param name="e">The event object</param>
        if (!e.unit) {
            return;
        }
        var unit = e.unit;
        var flags = unit.ScriptFlags;
        var death_message = flags.get("death_message");
        if (death_message) {
            var msg = chooseFromARandomSelectString(death_message);
            say(msg.format(unit.GetName()));
        }
        var unloads_transport_on_death = flags.get("unloads_cargo_on_death");
        if (unloads_transport_on_death) {
            CB.Transportation.unload_all_transported_units({ target: unit });
        }
        if (flags.get("drops_item"))
            CB.ItemDrops.itemDrop(unit);
        if (flags.get("death_transformation")) {
            CB.Transformation.change_unit_type({ target: unit });
        }
    }
    CB.after_death = after_death;
    function after_perform_skill(e) {
        /// <summary>Handles after skill performed logic</summary>
        var source = e.source_unit;
        var target = e.target_unit;
        var tile = e.target_tile;
        var skill = e.skill;
        var success = e.success;
        var flags = skill.ScriptFlags;
        if (flags.get("enters_transport")) {
            CB.Transportation.load_unit_into_transport({ source: source, target: target, skill: skill, success: success });
        }
        // for "loads_into_transport" which loads the target into the source, we just reverse the arguments
        if (flags.get("loads_onto_transport")) {
            CB.Transportation.load_unit_into_transport({ source: target, target: source, skill: skill, success: success });
        }
        if (flags.get("unloads_transport")) {
            CB.Transportation.unload_all_transported_units({ source: source, target: target, tile: tile, skill: skill, success: success });
        }
        var adjusts_transport_capacity = flags.get("adjusts_transport_capacity");
        if (adjusts_transport_capacity) {
            CB.Transportation.adjust_transport_capacity({ target: target, adjustment: adjusts_transport_capacity });
        }
        if (flags.get("untoggle_team")) {
            CB.TeamChanging.untoggle_team(target);
        }
        if (flags.get("revert_transformation")) {
            var newUnit = CB.Transformation.revert_unit_type({ target: target, skill: skill, success: success });
            if (newUnit)
                target = newUnit; // so that subsequent stuff happens to the right unit
        }
        if (flags.get("transform_into")) {
            var newUnit = CB.Transformation.change_unit_type({ source: source, target: target, skill: skill, success: success });
            if (newUnit)
                target = newUnit; // so that subsequent stuff happens to the right unit
        }
        var hasToggleTeamFlag = flags.get("toggle_to_user_team") || flags.get("charm") || flags.get("toggle_to_team");
        if (hasToggleTeamFlag) {
            CB.TeamChanging.toggle_team({ source: source, target: target, skill: skill, success: success });
        }
        if (flags.get("stack_into")) {
            var stackedUnit = CB.Stacking.stackUnits({ source: source, target: target, skill: skill, success: success });
            if (stackedUnit)
                target = stackedUnit;
        }
        if (flags.get("move_other_unit_away")) {
            var tiles = flags.get("move_other_unit_away");
            move_other_unit({ where: "away", source: source, target: target, tiles: tiles, skill: skill, success: success });
        }
        if (flags.get("move_other_unit_toward")) {
            var tiles = flags.get("move_other_unit_toward");
            move_other_unit({ where: "toward", source: source, target: target, tiles: tiles, skill: skill, success: success });
        }
        if (success && flags.get("success_message")) {
            var msg = chooseFromARandomSelectString(flags.get("success_message"));
            if (target)
                say(msg.format(source.GetName(), target.GetName()));
            else
                say(msg.format(source.GetName(), shared.GetPos(tile)));
        }
        if (!success && flags.get("failure_message")) {
            var msg = chooseFromARandomSelectString(flags.get("failure_message"));
            if (target)
                say(msg.format(source.GetName(), target.GetName()));
            else
                say(msg.format(source.GetName()));
        }
    }
    CB.after_perform_skill = after_perform_skill;
    function on_map_key(e) {
        /// <summary>Handles map key ppress logic</summary>
        var key = e.k;
        var mod = e.m;
        var map = shared.Map;
        var flags = map.ScriptFlags;
    }
    CB.on_map_key = on_map_key;
    function after_effect_applied(e) {
        var source = e.source_unit;
        var target = e.target_unit;
        var targetTile = e.target_tile;
        var effect = e.effect;
        var flags = effect.ScriptFlags;
        if (flags.get("transform_into") && target != null) {
            var newUnit = CB.Transformation.change_unit_type({ source: source, target: target, effect: effect });
            if (newUnit)
                target = newUnit;
        }
        var hasToggleTeamFlag = flags.get("toggle_to_user_team") || flags.get("charm") || flags.get("toggle_to_team");
        if (hasToggleTeamFlag) {
            CB.TeamChanging.toggle_team({ source: source, target: target, effect: effect });
        }
    }
    CB.after_effect_applied = after_effect_applied;
    function after_effect_removed(e) {
        var source = e.source_unit;
        var target = e.target_unit;
        var targetTile = e.target_tile;
        var effect = e.effect;
        var flags = effect.ScriptFlags;
        var hasToggleTeamFlag = flags.get("toggle_to_user_team") || flags.get("charm") || flags.get("toggle_to_team");
        if (hasToggleTeamFlag && target != null) {
            CB.TeamChanging.untoggle_team(target);
        }
        if (flags.get("transform_into") && target != null) {
            var newUnit = CB.Transformation.revert_unit_type({ target: target, effect: effect });
            if (newUnit)
                target = newUnit;
        }
    }
    CB.after_effect_removed = after_effect_removed;
    function after_effect_fizzled(e) {
        var target = e.target_unit;
        var tile = e.target_tile;
        var effect = e.effect;
        var flags = effect.ScriptFlags;
        var hasToggleTeamFlag = flags.get("toggle_to_user_team") || flags.get("charm") || flags.get("toggle_to_team");
        if (hasToggleTeamFlag && target != null) {
            CB.TeamChanging.untoggle_team(target);
        }
        var fizzleSound = flags.get("fizzle_sound");
        if (fizzleSound) {
            sound(fizzleSound);
        }
        var fizzleMessage = flags.get("fizzle_message");
        if (fizzleMessage) {
            var msg = chooseFromARandomSelectString(fizzleMessage);
            say(msg.format(target.GetName()));
        }
    }
    CB.after_effect_fizzled = after_effect_fizzled;
    function override_handle_causers(e) {
        var effecter = e.effecter;
        var skill = e.skill;
        var item = e.item;
        var effect = e.effect;
        var source = e.cur_unit;
        var target = e.target_unit;
        var targetTile = e.target_tile;
        var flags = effecter.ScriptFlags;
        if (flags.get("critical_hit_chance")) {
            var wasCritical = CB.CriticalHits.handleCritical(e);
            return { handled: wasCritical };
        }
        return { handled: false }; // handled indicates this script decided to handle it and the engine should not do any further perform skill logic.
    }
    CB.override_handle_causers = override_handle_causers;
    function after_turn_started(e) {
        // this is a good place to do reversion of transformed units, is too heavy to occur in after_fizzled
        revertAllExpiredTransformations();
    }
    CB.after_turn_started = after_turn_started;
    function override_tb_unit_ai(e) {
        var unit = e.unit;
        var handled, didSomething;
        var flags = unit.ScriptFlags;
        /*
        if (flags.get("ai_skill_useage_preference_order")) {
            var out = AI.preferencialSkillOrder(e);
            
        }
        */
        return { handled: handled, did_something: didSomething };
        // handled indicates this script decided to handle it and the engine should not do any further AI logic for this unit.
        // did_something indicates whether this unit performed an action or not.
    }
    CB.override_tb_unit_ai = override_tb_unit_ai;
    // helper functions for internal use mostly
    function calculateDirectionInt(sourceTile, targetTile) {
        if (sourceTile.Y == targetTile.Y) {
            if (sourceTile.X < targetTile.X) {
                return 6;
            }
            else if (sourceTile.X > targetTile.X) {
                return 2;
            }
            else {
                return;
            }
        }
        else if (sourceTile.Y < targetTile.Y) {
            if (sourceTile.X == targetTile.X) {
                return 0;
            }
            else if (sourceTile.X > targetTile.X) {
                return 1;
            }
            else {
                return 7;
            }
        }
        else {
            if (sourceTile.X == targetTile.X) {
                return 4;
            }
            else if (sourceTile.X > targetTile.X) {
                return 3;
            }
            else {
                return 5;
            }
        }
    }
    function getNextTileInDirection(currentTile, direction) {
        var x = currentTile.X, y = currentTile.Y;
        var mapWidth = shared.Map.Width - 1, mapHeight = shared.Map.Height - 1;
        switch (direction) {
            case 0:
                if (y > 0) {
                    return shared.Map.GetTile(x, y - 1);
                }
                else {
                    return currentTile;
                }
                break;
            case 1:
                if (x < mapWidth && y > 0) {
                    return shared.Map.GetTile(x + 1, y - 1);
                }
                else {
                    return currentTile;
                }
                break;
            case 2:
                if (x < mapWidth) {
                    return shared.Map.GetTile(x + 1, y);
                }
                else {
                    return currentTile;
                }
                break;
            case 3:
                if (x < mapWidth && y < mapHeight) {
                    return shared.Map.GetTile(x + 1, y + 1);
                }
                else {
                    return currentTile;
                }
                break;
            case 4:
                if (y < mapHeight) {
                    return shared.Map.GetTile(x, y + 1);
                }
                else {
                    return currentTile;
                }
                break;
            case 5:
                if (x > 0 && y < mapHeight) {
                    return shared.Map.GetTile(x - 1, y + 1);
                }
                else {
                    return currentTile;
                }
                break;
            case 6:
                if (x > 0) {
                    return shared.Map.GetTile(x - 1, y);
                }
                else {
                    return currentTile;
                }
                break;
            case 7:
                if (x > 0 && y > 0) {
                    return shared.Map.GetTile(x - 1, y - 1);
                }
                else {
                    return currentTile;
                }
                break;
            default:
                break;
        }
    }
    // not currently being used, but will be a nice thing to have working for code cleanup
    // need to run tests after release is done
    function getStandardEventProperties(event, argObject) {
        if (event.source_unit) {
            argObject.source_unit = event.source_unit;
        }
        if (event.target_unit) {
            argObject.target_unit = event.target_unit;
        }
        if (event.target_tile) {
            argObject.target_tile = event.target_tile;
        }
        if (event.unit) {
            argObject.unit = event.unit;
        }
        if (event.success) {
            argObject.success = event.success;
        }
    }
    function applyObjectToEvent(event, args, firstFormatArgument, secondFormatArgument) {
        for (var arg in args) {
            var argValue = args[arg];
            if (typeof argValue === 'string' && argValue.indexOf('{0}') != -1) {
                argValue = argValue.replace("{0}", firstFormatArgument);
            }
            if (typeof argValue === 'string' && argValue.indexOf('{1}') != -1) {
                argValue = argValue.replace("{1}", secondFormatArgument);
            }
            event[arg] = argValue;
        }
        return event;
    }
    CB.applyObjectToEvent = applyObjectToEvent;
    function chooseFromARandomSelectString(original, separator, forbidden) {
        if (!separator || separator == "")
            separator = "|";
        if (original.indexOf(separator) == -1)
            return original;
        var choices = original.split(separator);
        // if we've passed in some forbidden choices, remove them from the choices
        if (forbidden && choices) {
            for (var index = choices.length - 1; index >= 0; index--) {
                var choice = choices[index];
                // indexOf should work to tell us if the forbidden array contains this choice
                if (forbidden.indexOf(choice) > -1) {
                    // splice can be used on an array in this way to remove elements
                    choices.splice(index, 1);
                }
            }
        }
        var randomIndex = Math.round(Math.random() * (choices.length - 1));
        var pick = choices[randomIndex];
        return pick;
    }
    CB.chooseFromARandomSelectString = chooseFromARandomSelectString;
    function revertAllExpiredTransformations() {
        var allUnits = shared.Map.AllUnits;
        var currRound = shared.Map.CurrentRound;
        var currTurn = shared.Map.CurrentTurn;
        for (var index = 0; index < allUnits.length; index++) {
            var unit = allUnits[index];
            var effects = unit.Effects;
            for (var index2 = 0; index2 < effects.length; index2++) {
                var effect = effects[index2];
                if (effect.RoundExpires != currRound || effect.TurnExpires != currTurn)
                    continue;
                // this effect will expire this round. check if its a transform effect
                if (effect.ScriptFlags.get("transform_into")) {
                    // its a transform effect. revert the unit and remove the effect from the old unit
                    var oldUnit = CB.Transformation.revert_unit_type({ target: unit, effect: effect });
                    if (oldUnit) {
                        for (var index3 = 0; index3 < oldUnit.Effects.length; index3++) {
                            var oldEffect = oldUnit.Effects[index3];
                            if (oldEffect.IndexedName == effect.IndexedName) {
                                // its the right effect, remove it
                                oldUnit.Effects.Remove(oldEffect);
                            }
                        }
                    }
                    say("{0} fizzles from {1}".format(effect.FriendlyName, unit.GetName()));
                    var fizzleSound = effect.ScriptFlags.get("fizzle_sound");
                    if (fizzleSound) {
                        sound(fizzleSound);
                    }
                    var fizzleMessage = effect.ScriptFlags.get("fizzle_message");
                    if (fizzleMessage) {
                        var msg = chooseFromARandomSelectString(fizzleMessage);
                        say(msg.format(unit.GetName()));
                    }
                }
            }
        }
    }
    // a version of String.Format found on StackOverflow
    // First, checks if it isn't implemented yet.
    if (!String.prototype.format) {
        String.prototype.format = function () {
            var args = arguments;
            return this.replace(/{(\d+)}/g, function (match, number) {
                return typeof args[number] != 'undefined' ? args[number] : match;
            });
        };
    }
})(CB || (CB = {}));
